package com.github.bluesbruce.websocket;

import java.util.Map;
import javax.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.messaging.handler.annotation.DestinationVariable;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.annotation.SendToUser;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.simp.user.SimpUserRegistry;
import org.springframework.stereotype.Controller;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;

/**
 * 服务端处理消息
 * <p>公告就是只要订阅了此路径的的用户都能收到，使用@SendTo注解实现。<br>
 * 私信就是指定人员才能收到，可以用@SendToUser注解或者 SimpMessagingTemplate模板类的convertAndSendToUser进行处理</p>
 * <p>在spring 框架内部以"/user" 为前缀的消息将会通过 UserDestinationMessageHandler 进行处理</p>
 *
 * @author BBF
 */
@Controller
public class WebSocketStompController {

  private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketStompController.class);

  @Resource
  private SimpMessagingTemplate template;

  /**
   * 当前连接用户
   */
  @Resource
  private SimpUserRegistry userRegistry;

  /**
   * 发送广播消息，所有订阅了此路径的用户都会收到此消息
   *
   * @param groupId 消息接受组，@DestinationVariable注解作用类似于MVC的@PathVariable
   * @param msg 消息内容
   * @param headerAccessor STOMP协议头访问器
   * @return 消息内容JSON
   */
  @MessageMapping("/sendChatMsg/{groupId}")
  @SendTo("/topicTest/hello")
  public String sendAll(@DestinationVariable(value = "groupId") String groupId,
      String msg, StompHeaderAccessor headerAccessor) {
    // 这里拿到的user对象是在WebSocketChannelInterceptor拦截器中绑定上的对象
    WebSocketUserAuthentication user = (WebSocketUserAuthentication) headerAccessor.getUser();
    LOGGER.info("sendAll中获取用户登录令牌 = {},客户端传递分组参数 = {}", user.getName(), groupId);
    return new WebSocketMessage(msg).toJson();
  }

  /**
   * 发送私信消息
   * <p>如果相同账号打开了多个浏览器窗口（即多个WebSocket session通道），
   * 默认会把消息推送到同一个账号不同的session， 可以利用broadcast = false把避免推送到所有的session中</p>
   *
   * @param msg 消息内容
   * @param headerAccessor STOMP协议头访问器
   * @return 消息内容JSON
   */
  @MessageMapping("/sendChatMsgByOwn")
  @SendToUser(value = "/userTest/own")
  public String sendOwn(String msg, StompHeaderAccessor headerAccessor) {
    // 这里拿到的user对象是在WebSocketChannelInterceptor拦截器中绑定上的对象
    WebSocketUserAuthentication user = (WebSocketUserAuthentication) headerAccessor.getUser();
    if ("welcome".equals(msg)) {
      int count = userRegistry.getUserCount();
      msg = "连接成功，当前在线用户" + count + "人";
    }
    LOGGER.info("sendOwn中获取用户登录令牌 = {}, socketId = {}",
        user.getName(), headerAccessor.getSessionId());
    return new WebSocketMessage(msg).toJson();
  }

  /**
   * 发送消息给指定ID
   * <p>这里用了 @SendToUser 和 返回值 其意义是可以在发送成功后回执给发送放其信息发送成功</p>
   *
   * @param accountId 消息接收人ID，还可以在header中封装用@Header或者@Headers去取等多种方式
   * @param msg 消息内容
   * @param headerAccessor STOMP协议头访问器
   * @return 消息内容JSON
   */
  @MessageMapping("/sendChatMsgById/{accountId}")
  @SendToUser(value = "/userTest/callBack")
  public String sendById(@DestinationVariable(value = "accountId") String accountId,
      String msg, StompHeaderAccessor headerAccessor) {
    // 这里拿到的user对象是在WebSocketChannelInterceptor拦截器中绑定上的对象
    WebSocketUserAuthentication user = (WebSocketUserAuthentication) headerAccessor.getUser();
    LOGGER.info("sendById中获取用户登录令牌 = {}, socketId = {}",
        user.getName(), headerAccessor.getSessionId());

    // 向用户发送消息,第一个参数是接收人、第二个参数是浏览器订阅的地址，第三个是消息本身
    // 如果服务端要将消息发送给特定的某一个用户，
    // 可以使用SimpleMessageTemplate的convertAndSendToUser方法(第一个参数是用户的登陆名username)
    String address = "/userTest/callBack";
    // 给接受者发送消息
    template.convertAndSendToUser(accountId, address, msg);
    // 给发送者的反馈
    return new WebSocketMessage("发送成功").toJson();
  }

  /**
   * 获取HttpSession中的SessionID
   *
   * @param headerAccessor STOMP协议头访问器
   * @return SessionID
   */
  private String getSessionId(StompHeaderAccessor headerAccessor) {
    Map<String, Object> attributes = headerAccessor.getSessionAttributes();
    return (String) attributes
        .get(HttpSessionHandshakeInterceptor.HTTP_SESSION_ID_ATTR_NAME);
  }

}
